﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using IInject.Extensibility;
using IInject.Internal;
using IInject.Registration;
using IInject.Registration.Scanning;
using IInject.Resolution;
using IInject.Resolution.Activation;

namespace IInject
{
	/// <summary>
	/// 
	/// </summary>
	public class Container : DisposableObject, IContainer, IExtensible
	{
		#region Fields

		private readonly IList<IExtension> _extensions = new List<IExtension>();

		private IRegistryProvider _registryProvider;
		private ITypeDependencyActivator _dependencyActivator;
		private ITypeDependencyResolver _dependencyResolver;

		#endregion

		#region Constructors

		/// <summary>
		/// Initializes a new instance of the <see cref="Container"/> class.
		/// </summary>
		public Container()
		{
			//ContainerProvider.SetCurrent(this);
			ContainerInitializer.Initialize(this);
		}

		#endregion

		#region Properties

		/// <summary>
		/// Gets the <see cref="IRegistryProvider" /> responsible for registering all types.
		/// </summary>
		public IRegistryProvider Registry
		{
			get
			{
				if (_registryProvider == null)
				{
					_registryProvider = GetRegistryProvider();
				}

				return _registryProvider;
			}
			set
			{
				_registryProvider = value;
			}
		}

		/// <summary>
		/// Gets the <see cref="ITypeDependencyActivator" /> responsible for creating all dependencies.
		/// </summary>
		public ITypeDependencyActivator Activator
		{
			get
			{
				if (_dependencyActivator == null)
				{
					_dependencyActivator = GetDependencyActivator();
				}

				return _dependencyActivator;
			}
			set
			{
				_dependencyActivator = value;
			}
		}

		/// <summary>
		/// The event that is raised when a context is registering.
		/// </summary>
		public event Action<RegistryContext> ItemRegistering;

		/// <summary>
		/// The event that is raised when a context is registered.
		/// </summary>
		public event Action<RegistryContext> ItemRegistered;

		/// <summary>
		/// The event that is raised when a context is resolving.
		/// </summary>
		public event Action<TypeDependency> ItemResolving;

		/// <summary>
		/// Gets the <see cref="ITypeDependencyResolver" /> responsible for resolving all dependencies.
		/// </summary>
		public ITypeDependencyResolver Resolver
		{
			get
			{
				if (_dependencyResolver == null)
				{
					_dependencyResolver = GetDependencyResolver();
				}

				return _dependencyResolver;
			}
			set
			{
				_dependencyResolver = value;
			}
		}

		#endregion

		#region Methods

		#region Extensibility

		/// <summary>
		/// Adds the extension.
		/// </summary>
		/// <param name="extension">The extension.</param>
		public void AddExtension(IExtension extension)
		{
			if (!_extensions.Contains(extension))
			{
				_extensions.Add(extension);
			}
		}

		/// <summary>
		/// Adds the extension.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="extension">The extension.</param>
		public void AddExtension<T>(T extension) where T : IExtension
		{
			AddExtension((IExtension)extension);
		}

		/// <summary>
		/// Removes the extension.
		/// </summary>
		/// <param name="extension">The extension.</param>
		/// <returns></returns>
		public bool RemoveExtension(IExtension extension)
		{
			return _extensions.Remove(extension);
		}

		/// <summary>
		/// Removes the extension.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="extension">The extension.</param>
		/// <returns></returns>
		public bool RemoveExtension<T>(T extension) where T : IExtension
		{
			return RemoveExtension((IExtension)extension);
		}

		/// <summary>
		/// Returns an enumerator that iterates through the collection.
		/// </summary>
		/// <returns>
		/// A <see cref="T:System.Collections.Generic.IEnumerator`1" /> that can be used to iterate through the collection.
		/// </returns>
		public IEnumerator<IExtension> GetEnumerator()
		{
			return _extensions.GetEnumerator();
		}

		/// <summary>
		/// Returns an enumerator that iterates through a collection.
		/// </summary>
		/// <returns>
		/// An <see cref="T:System.Collections.IEnumerator" /> object that can be used to iterate through the collection.
		/// </returns>
		IEnumerator IEnumerable.GetEnumerator()
		{
			return GetEnumerator();
		}

		#endregion

		#region Assembly Scanning

		/// <summary>
		/// Loads and scans the assembly with the provided name.
		/// </summary>
		/// <param name="assemblyName">The name of the assembly to scan.</param>
		public virtual IContainer Scan(string assemblyName)
		{
			return Scan(a => a.Scan(assemblyName));
		}

		/// <summary>
		/// Loads and scans the assembly with the provided <see cref="AssemblyName"/>.
		/// </summary>
		/// <param name="assemblyName">The <see cref="AssemblyName"/> of the assembly to scan.</param>
		public virtual IContainer Scan(AssemblyName assemblyName)
		{
			return Scan(a => a.Scan(assemblyName));
		}

		/// <summary>
		/// Scans the provided <see cref="Assembly"/>.
		/// </summary>
		/// <param name="assembly">The <see cref="Assembly"/> to scan.</param>
		public virtual IContainer Scan(Assembly assembly)
		{
			return Scan(a => a.Scan(assembly));
		}

		/// <summary>
		/// Gives access to an AssemblyScanner that handles scanning assemblies.
		/// This allows for customizing each scan by providing <see cref="IScanningRule"/>s
		/// or just using the default <see cref="ScanRules"/>.
		/// </summary>
		/// <param name="scanner">The delegate using the AssemblyScanner</param>
		/// <returns><see cref="IContainer"/></returns>
		public virtual IContainer Scan(Action<IAssemblyScanner> scanner)
		{
			if (scanner == null)
			{
				throw new ArgumentNullException("scanner");
			}

			scanner(new DefaultAssemblyScanner(this));

			return this;
		}

		#endregion

		#region Get Methods

		///<summary>
		/// Injects the dependencies into the type and returns the initialized object.
		///</summary>
		///<param name="type">The type to return.</param>
		///<returns>The resolved type object.</returns>
		public virtual object Get(Type type)
		{
			return Get(type, null);
		}

		///<summary>
		/// Injects the dependencies into the type and returns the initialized object.
		///</summary>
		///<param name="type">The registered <see cref="Type"/>.</param>
		///<param name="key">The key associated with the registered type, null if no key is required.</param>
		///<returns>The resolved type.</returns>
		public virtual object Get(Type type, string key)
		{
			var dependency = Resolver.Resolve(type, key);
			((IExtensible)this).OnContextResolving(dependency);
			return Activator.Activate(dependency);
		}

		///<summary>
		/// Injects the dependencies into the type and returns the initialized object.
		///</summary>
		///<typeparam name="T">The registered type <typeparamref name="T"/>.</typeparam>
		///<returns>The resolved type <see cref="T"/>.</returns>
		public virtual T Get<T>()
		{
			return (T)Get(typeof(T), null);
		}

		///<summary>
		/// Injects the dependencies into the type and returns the initialized object.
		///</summary>
		///<param name="key">The key associated with the registered type, null if no key is required.</param>
		///<typeparam name="T">The registered type <typeparamref name="T"/>.</typeparam>
		///<returns>The resolved type <see cref="T"/>.</returns>
		public virtual T Get<T>(string key)
		{
			return (T)Get(typeof(T), key);
		}

		///<summary>
		/// Injects the dependencies into the types and returns them.
		///</summary>
		///<typeparam name="T">The registered types <typeparamref name="T"/>.</typeparam>
		///<returns>The resolved types <see cref="T"/>.</returns>
		public virtual IEnumerable<T> GetAll<T>()
		{
            return GetAll<T>(null);
        }

		///<summary>
		/// Injects the dependencies into the types and returns them.
		///</summary>
		///<typeparam name="T">The registered types <typeparamref name="T"/>.</typeparam>
		///<returns>The resolved types <see cref="T"/>.</returns>
		public virtual IEnumerable<T> GetAll<T>(string key)
        {
            var results = new List<T>();
			var dependencies = Resolver.ResolveAll(typeof(T), key);
			var extensible = ((IExtensible)this);

            foreach (var item in dependencies)
			{
				extensible.OnContextResolving(item);
                results.Add((T)Activator.Activate(item));
            }

            return results;
        }

		///<summary>
		/// Injects the dependencies into the types and returns them.
		///</summary>
		///<param name="type">The registered <see cref="Type"/>s.</param>
		///<returns>The resolved types.</returns>
		public virtual IEnumerable<object> GetAll(Type type)
        {
            return GetAll(type, null);
        }

		/// <summary>
		///  Injects the dependencies into the types and returns them.
		/// </summary>
		///<param name="type">The registered <see cref="Type"/>s.</param>
		///<param name="key">The key associated with the registered type, null if no key is required.</param>
		/// <returns>The resolved types.</returns>
		public virtual IEnumerable<object> GetAll(Type type, string key)
		{
			var results = new List<object>();
			var dependencies = Resolver.ResolveAll(type, key);
			var extensible = ((IExtensible)this);

			foreach (var item in dependencies)
			{
				extensible.OnContextResolving(item);
				results.Add(Activator.Activate(item));
			}

			return results;
		}

		#endregion

		#region Map Generics

		///<summary>
		///</summary>
		///<typeparam name="T">The type to register.</typeparam>
		///<returns>An <see cref="IContextBuilder"/> used to configure and register the type.</returns>
		public virtual IContextBuilder<T> Map<T>()
		{
			return new GenericContextBuilder<T>(this);
		}

		///<summary>
		///</summary>
		///<typeparam name="TFrom">The type to register.</typeparam>
		///<typeparam name="TTo">The type returned when resolved.</typeparam>
		///<returns>An <see cref="IContextBuilder"/> used to configure and register the type.</returns>
		public virtual IContextBuilder<TFrom> Map<TFrom, TTo>() where TTo : class, TFrom
		{
			return new GenericContextBuilder<TFrom>(this).To<TTo>();
		}

		///<summary>
		///</summary>
		///<param name="type"></param>
		///<returns>An <see cref="IContextBuilder"/> used to configure and register the type.</returns>
		public virtual IContextBuilder Map(Type type)
		{
			return new DefaultContextBuilder(type, this);
		}

		///<summary>
		///</summary>
		///<param name="registerAction"></param>
		///<returns>An <see cref="IContextBuilder"/> used to configure and register the type.</returns>
		public IContainer Map(Action<IContextBuilder> registerAction)
		{
			using (var builder = new DefaultContextBuilder(this))
			{
				registerAction(builder);
				return builder.Apply();
			}
		}

		public virtual IContainer Map<T>(Action<IContextBuilder<T>> registerAction)
		{
			using (var builder = Map<T>())
			{
				registerAction(builder);
				return builder.Apply();
			}
		}

		#endregion

		#region Set Generics

		///<summary>
		///</summary>
		///<typeparam name="T">The type to register.</typeparam>
		///<returns></returns>
		public virtual IContainer Set<T>() where T : class
		{
			return Set(typeof(T), null, null, Scope.PerResolve);
		}

		///<summary>
		///</summary>
		///<param name="key">The key associated with the registered type, null if no key is required.</param>
		///<typeparam name="T">The type to register.</typeparam>
		///<returns></returns>
		public virtual IContainer Set<T>(string key) where T : class
		{
			return Set(typeof(T), key, null, Scope.PerResolve);
		}

		///<summary>
		///</summary>
		///<param name="scope">The <see cref="Scope"/> of how this type will be resolved.</param>
		///<typeparam name="T">The type to register.</typeparam>
		///<returns></returns>
		public virtual IContainer Set<T>(Scope scope) where T : class
		{
			return Set(typeof(T), null, null, scope);
		}

		///<summary>
		///</summary>
		///<param name="key">The key associated with the registered type, null if no key is required.</param>
		///<param name="scope">The <see cref="Scope"/> of how this type will be resolved.</param>
		///<typeparam name="T">The type to register.</typeparam>
		///<returns></returns>
		public virtual IContainer Set<T>(string key, Scope scope) where T : class
		{
			return Set(typeof(T), key, null, scope);
		}

		///<summary>
		///</summary>
		///<param name="instance"></param>
		///<typeparam name="T">The type to register.</typeparam>
		///<returns></returns>
		public virtual IContainer Set<T>(T instance) where T : class
		{
			return Set(typeof(T), null, instance, Scope.Singleton);
		}

		/// <summary>
		/// </summary>
		/// <typeparam name="T">The type to register.</typeparam>
		/// <param name="key">The key associated with the registered type, null if no key is required.</param>
		/// <param name="instance"></param>
		/// <returns></returns>
		public virtual IContainer Set<T>(string key, T instance) where T : class
		{
			return Set(typeof(T), key, instance, Scope.Singleton);
		}

		/// <summary>
		/// </summary>
		/// <typeparam name="T">The type to register.</typeparam>
		/// <param name="instance"></param>
		/// <param name="scope">The <see cref="Scope" /> of how this type will be resolved.</param>
		/// <returns></returns>
		public virtual IContainer Set<T>(T instance, Scope scope) where T : class
		{
			return Set(typeof(T), null, instance, scope);
		}

		/// <summary>
		/// </summary>
		/// <typeparam name="T">The type to register.</typeparam>
		/// <param name="key">The key associated with the registered type, null if no key is required.</param>
		/// <param name="instance"></param>
		/// <param name="scope">The <see cref="Scope" /> of how this type will be resolved.</param>
		/// <returns></returns>
		public virtual IContainer Set<T>(string key, T instance, Scope scope) where T : class
		{
			return Set(typeof(T), key, instance, scope);
		}

		/// <summary>
		/// </summary>
		/// <typeparam name="TFrom"></typeparam>
		/// <typeparam name="TTo"></typeparam>
		/// <returns></returns>
		public virtual IContainer Set<TFrom, TTo>() where TTo : class, TFrom
		{
			return Set(typeof(TFrom), typeof(TTo), null, Scope.PerResolve);
		}

		/// <summary>
		/// </summary>
		/// <typeparam name="TFrom"></typeparam>
		/// <typeparam name="TTo"></typeparam>
		/// <param name="key">The key associated with the registered type, null if no key is required.</param>
		/// <returns></returns>
		public virtual IContainer Set<TFrom, TTo>(string key) where TTo : class, TFrom
		{
			return Set(typeof(TFrom), typeof(TTo), key, Scope.PerResolve);
		}

		/// <summary>
		/// </summary>
		/// <typeparam name="TFrom"></typeparam>
		/// <typeparam name="TTo"></typeparam>
		/// <param name="scope">The <see cref="Scope" /> of how this type will be resolved.</param>
		/// <returns></returns>
		public virtual IContainer Set<TFrom, TTo>(Scope scope) where TTo : class, TFrom
		{
			return Set(typeof(TFrom), typeof(TTo), null, scope);
		}

		/// <summary>
		/// </summary>
		/// <typeparam name="TFrom"></typeparam>
		/// <typeparam name="TTo"></typeparam>
		/// <param name="key">The key associated with the registered type, null if no key is required.</param>
		/// <param name="scope">The <see cref="Scope" /> of how this type will be resolved.</param>
		/// <returns></returns>
		public virtual IContainer Set<TFrom, TTo>(string key, Scope scope) where TTo : class, TFrom
		{
			return Set(typeof(TFrom), typeof(TTo), key, scope);
		}

		/// <summary>
		/// </summary>
		/// <typeparam name="T">The type to register.</typeparam>
		/// <param name="items"></param>
		/// <returns></returns>
		public virtual IContainer SetAll<T>(IEnumerable<T> items)
		{
			return SetAll(typeof(T), null, items);
		}

		/// <summary>
		/// </summary>
		/// <typeparam name="T">The type to register.</typeparam>
		/// <param name="key"></param>
		/// <param name="items"></param>
		/// <returns></returns>
        public virtual IContainer SetAll<T>(string key, IEnumerable<T> items)
        {
            return SetAll(typeof(T), key, items);
        }

		/// <summary>
		/// </summary>
		/// <typeparam name="TFrom"></typeparam>
		/// <typeparam name="TTo"></typeparam>
		/// <param name="items"></param>
		/// <returns></returns>
		public virtual IContainer SetAll<TFrom, TTo>(IEnumerable<TTo> items) where TTo : TFrom
		{
			return SetAll(typeof(TFrom), null, items);
		}

		/// <summary>
		/// </summary>
		/// <typeparam name="TFrom"></typeparam>
		/// <typeparam name="TTo"></typeparam>
		/// <param name="key"></param>
		/// <param name="items"></param>
		/// <returns></returns>
        public virtual IContainer SetAll<TFrom, TTo>(string key, IEnumerable<TTo> items) where TTo : TFrom
        {
            return SetAll(typeof(TFrom), key, items);
        }

		/// <summary>
		/// Sets all.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="from">From.</param>
		/// <param name="items">The items.</param>
		/// <returns></returns>
		public virtual IContainer SetAll<T>(Type from, IEnumerable<T> items)
		{
            return SetAll(from, null, items);
		}

		/// <summary>
		/// </summary>
		/// <typeparam name="T">The type to register.</typeparam>
		/// <param name="from"></param>
		/// <param name="key"></param>
		/// <param name="items"></param>
		/// <returns></returns>
        public virtual IContainer SetAll<T>(Type from, string key, IEnumerable<T> items)
        {
            using (var builder = new EnumerableContextBuilder<T>(from, this))
            {
                foreach (var item in items)
                {
                    builder.Add(item);
                }

                return builder.With(key).Apply();
            }
        }

		/// <summary>
		/// </summary>
		/// <typeparam name="T">The type to register.</typeparam>
		/// <param name="registerAction"></param>
		/// <returns></returns>
		public virtual IContainer SetAll<T>(Action<IEnumerableContextBuilder<T>> registerAction)
		{
			using (var builder = new EnumerableContextBuilder<T>(typeof(T), this))
			{
				registerAction(builder);
				return builder.Apply();
			}
		}

		/// <summary>
		/// </summary>
		/// <typeparam name="TFrom"></typeparam>
		/// <typeparam name="TTo"></typeparam>
		/// <param name="registerAction"></param>
		/// <returns></returns>
		public virtual IContainer SetAll<TFrom, TTo>(Action<IEnumerableContextBuilder<TTo>> registerAction)
		{
			using (var builder = new EnumerableContextBuilder<TTo>(typeof(TFrom), this))
			{
				registerAction(builder);
				return builder.Apply();
			}
		}

		#endregion

		#region Set Non-Generics

		/// <summary>
		/// </summary>
		/// <param name="type"></param>
		/// <returns></returns>
		public virtual IContainer Set(Type type)
		{
			return Set(type, type, null, Scope.PerResolve);
		}

		/// <summary>
		/// </summary>
		/// <param name="type"></param>
		/// <param name="key">The key associated with the registered type, null if no key is required.</param>
		/// <returns></returns>
		public virtual IContainer Set(Type type, string key)
		{
			return Set(type, type, key,	Scope.PerResolve);
		}

		/// <summary>
		/// </summary>
		/// <param name="type"></param>
		/// <param name="scope">The <see cref="Scope" /> of how this type will be resolved.</param>
		/// <returns></returns>
		public virtual IContainer Set(Type type, Scope scope)
		{
			return Set(type, type, null, scope);
		}

		/// <summary>
		/// </summary>
		/// <param name="type"></param>
		/// <param name="key">The key associated with the registered type, null if no key is required.</param>
		/// <param name="scope">The <see cref="Scope" /> of how this type will be resolved.</param>
		/// <returns></returns>
		public virtual IContainer Set(Type type, string key, Scope scope)
		{
			return Set(type, type, key, scope);
		}

		/// <summary>
		/// </summary>
		/// <param name="type"></param>
		/// <param name="instance"></param>
		/// <returns></returns>
		public virtual IContainer Set(Type type, object instance)
		{
			return Set(type, null, instance, Scope.Singleton);
		}

		/// <summary>
		/// </summary>
		/// <param name="type"></param>
		/// <param name="key">The key associated with the registered type, null if no key is required.</param>
		/// <param name="instance"></param>
		/// <returns></returns>
		public virtual IContainer Set(Type type, string key, object instance)
		{
			return Set(type, key, instance, instance == null ? Scope.PerResolve : Scope.Singleton);
		}

		/// <summary>
		/// </summary>
		/// <param name="type"></param>
		/// <param name="instance"></param>
		/// <param name="scope">The <see cref="Scope" /> of how this type will be resolved.</param>
		/// <returns></returns>
		public virtual IContainer Set(Type type, object instance, Scope scope)
		{
			return Set(type, null, instance, scope);
		}

		/// <summary>
		/// </summary>
		/// <param name="type"></param>
		/// <param name="key">The key associated with the registered type, null if no key is required.</param>
		/// <param name="instance"></param>
		/// <param name="scope">The <see cref="Scope" /> of how this type will be resolved.</param>
		/// <returns></returns>
		public virtual IContainer Set(Type type, string key, object instance, Scope scope)
		{
			using (var builder = new DefaultContextBuilder(type, this))
			{
				return builder.With(key).Return(instance).With(scope).Apply();
			}
		}

		/// <summary>
		/// </summary>
		/// <param name="from"></param>
		/// <param name="to"></param>
		/// <returns></returns>
		public virtual IContainer Set(Type from, Type to)
		{
			return Set(from, to, null, Scope.PerResolve);
		}

		/// <summary>
		/// </summary>
		/// <param name="from"></param>
		/// <param name="to"></param>
		/// <param name="key">The key associated with the registered type, null if no key is required.</param>
		/// <returns></returns>
		public virtual IContainer Set(Type from, Type to, string key)
		{
			return Set(from, to, key, Scope.PerResolve);
		}

		/// <summary>
		/// </summary>
		/// <param name="from"></param>
		/// <param name="to"></param>
		/// <param name="scope">The <see cref="Scope" /> of how this type will be resolved.</param>
		/// <returns></returns>
		public virtual IContainer Set(Type from, Type to, Scope scope)
		{
			return Set(from, to, null, scope);
		}

		/// <summary>
		/// </summary>
		/// <param name="from"></param>
		/// <param name="to"></param>
		/// <param name="key">The key associated with the registered type, null if no key is required.</param>
		/// <param name="scope">The <see cref="Scope" /> of how this type will be resolved.</param>
		/// <returns></returns>
		public virtual IContainer Set(Type from, Type to, string key, Scope scope)
		{
			using (var builder = new DefaultContextBuilder(from, this))
			{
				return builder.To(to).With(key).With(scope).Apply();
			}
		}

		#endregion

		#region Protected Methods

		/// <summary>
		/// Called when [context registered].
		/// </summary>
		/// <param name="context">The context.</param>
		void IExtensible.OnContextRegistered(RegistryContext context)
		{
			var handler = ItemRegistered;

			if (handler != null)
			{
				handler(context);
			}
		}

		/// <summary>
		/// Called when [context registering].
		/// </summary>
		/// <param name="context">The context.</param>
		void IExtensible.OnContextRegistering(RegistryContext context)
		{
			var handler = ItemRegistering;

			if (handler != null)
			{
				handler(context);
			}
		}

		/// <summary>
		/// Called when [context resolving].
		/// </summary>
		/// <param name="dependency">The dependency.</param>
		void IExtensible.OnContextResolving(TypeDependency dependency)
		{
			var handler = ItemResolving;

			if (handler != null)
			{
				handler(dependency);
			}
		}

		/// <summary>
		/// 
		/// </summary>
		/// <returns></returns>
		protected virtual IRegistryProvider GetRegistryProvider()
		{
			return new DefaultRegistryProvider(this);
		}

		/// <summary>
		/// 
		/// </summary>
		/// <returns></returns>
		protected virtual ITypeDependencyResolver GetDependencyResolver()
		{
			return new DefaultDependencyResolver(this);
		}

		/// <summary>
		/// 
		/// </summary>
		/// <returns></returns>
		protected virtual ITypeDependencyActivator GetDependencyActivator()
		{
			return new DefaultDependencyActivator();
		}

		/// <summary>
		/// Disposes of this instances' allocated resources.
		/// </summary>
		/// <param name="disposing"></param>
		protected override void Dispose(bool disposing)
		{
			if (disposing && _registryProvider != null)
			{
				_registryProvider.Dispose();
			}
		}

		#endregion

		#endregion
	}
}
